home *** CD-ROM | disk | FTP | other *** search
/ BCI NET / BCI NET Dec 94.iso / archives / programming / libraries / eagui11.lha / EAGUI / Tutorial.doc < prev   
Encoding:
Text File  |  1993-12-22  |  22.6 KB  |  524 lines

  1.  
  2. -----------------------------------------------------------------------------
  3.  
  4.                  Environment Adaptive Graphic User Interface
  5.  
  6. -----------------------------------------------------------------------------
  7. $RCSfile: Tutorial.doc,v $
  8. $Revision: 1.3 $
  9. $Date: 1993/12/15 14:24:26 $
  10. -----------------------------------------------------------------------------
  11.  
  12.  
  13.      Introduction
  14.      ¯¯¯¯¯¯¯¯¯¯¯¯
  15. The Environment Adaptive Graphic User Interface (EAGUI) is a system which
  16. allows you to build interfaces that, as the name suggests, adapt to the
  17. environment they're run in. It uses normal GadTools and BOOPSI gadgets, and
  18. does not modify them in any way. This allows programmers to implement EAGUI
  19. in existing applications easily.
  20.  
  21. This tutorial is a practical introduction to EAGUI. After you've read this,
  22. you should be able to write your own interfaces, using the AutoDocs
  23. (EAGUI.doc) as a reference. Apart from reading this document, you might also
  24. want to take a look at the examples. The tutorial assumes you are familiar
  25. with programming the AmigaOS[2], and some knowledge of C is useful too,
  26. because all examples are written in C.
  27.  
  28. This tutorial is based on a simple example. The full source of this example
  29. can be found in "example.c".
  30.  
  31.  
  32.      Concepts
  33.      ¯¯¯¯¯¯¯¯
  34. EAGUI divides the contents of a window in a hierarchical object tree. Objects
  35. can be either gadgets, images or groups. Groups can contain any number of
  36. objects. There are two types of groups: horizontal and vertical groups.
  37. Horizontal groups contain objects that are placed next to each other,
  38. vertical groups contain objects that are placed below each other. An object
  39. tree contains all information to create the gadgets and images in a window.
  40.  
  41. For example, let's look at a string requester window that contains a string
  42. gadget, and an "Ok" and "Cancel" button below it. Starting at the top of the
  43. tree, we define a vertical group to divide the window in two. The upper
  44. object will contain the string gadget, the lower will contain the buttons.
  45. The lower object in turn is a horizontal object, which contains three (!)
  46. objects: the "Ok" button, an empty spaceholder and the "Cancel" button. The
  47. complete tree now looks like this:
  48.  
  49.      Vertical group
  50.           String gadget
  51.           Horizontal group
  52.                "Ok" button gadget
  53.                Empty spaceholder
  54.                "Cancel" button gadget
  55.  
  56. This tree already holds some information about how objects should be placed
  57. in the window, but not enough. Let's assume we want to create a resizable
  58. window, since this is clearly the most difficult type of window to maintain.
  59.  
  60. A window can't be arbitrarily small, or else the objects won't fit inside the
  61. window. There is a minimum size. This size is determined by looking at the
  62. minimum size for each individual object in the window. We start at the bottom
  63. of the tree, where we find three objects. It is obvious that the labels of
  64. the buttons have to fit within the frame. The minimum size of these objects
  65. therefore is determined by the dimensions of the label. The other object, the
  66. empty spaceholder, is merely an object that fills up the empty space between
  67. the "Ok" and "Cancel" gadgets. This space can be arbitrarily small, so its
  68. minimum sizes are zero. Going up in the tree, we arrive at the Horizontal
  69. group object. The minimum sizes of its members are already determined, so the
  70. minimum sizes of the group are obtained by adding the horizontal minimum
  71. sizes of all children and taking the largest vertical size of the children.
  72. The next object, the string gadget, also has a minimum size that is
  73. determined by the dimensions of the string that has to fit inside the gadget.
  74. Finally, going up again, we arrive at the vertical group. Again, its members
  75. are known, so determining the size of the group is easy.
  76.      All size calculations can be done at runtime. This is necessary because
  77. an application can not know in advance what font or locale the user has
  78. selected, and there is no valid reason why the user should be forced to use a
  79. certain font or locale. To allow flexible size calculations, each object uses
  80. a method to determine its minimum size. This method is implemented as a Hook.
  81. If you want, you can write this Hook function yourself, but for normal
  82. gadgets, general purpose methods are already supplied. More information about
  83. these Hooks can be found lateron.
  84.      Apart from the minimum sizes, objects have borders, whose sizes can also
  85. be determined using methods if you want. Borders can be seen as whitespace
  86. around objects.
  87.  
  88. It can sometimes be hard to remember the exact relation between the size,
  89. border size and offset of an object. Therefore, we've supplied a simple IFF
  90. picture, which shows the relations graphically. This picture's called
  91. "Tutorial_1.pic".
  92.  
  93. Now that we know the minimum sizes, we also know the minimum inner size of
  94. the window. Please note that to determine the minimum size of the window, we
  95. have to add the sizes of the window borders. This presents us with a small
  96. problem, since we do not know the thickness of the borders in advance. There
  97. are several ways of dealing with this problem. These will be discussed
  98. lateron.
  99.  
  100. The next step is to actually open the window. After we've done that, we
  101. actually know the inner sizes of the window, and therefore we know the size
  102. of the root (top) object, which, in our example, is the vertical group. The
  103. object is set to this size. From hereon, the sizes of all other object are
  104. calculated.
  105.  
  106. We've already mentioned the minimum sizes, but there is another important
  107. object attribute we've left out up til now. Suppose that the window is bigger
  108. than the minimum size: how do we divide the available space? Some objects
  109. will want to use as much space as possible, while others will want to remain
  110. the same size. To solve this problem, each object can have a weight. This is
  111. a factor, which indicates how an object will grow in relation to other
  112. objects in the same group. Let's return to our example.
  113.  
  114. The vertical group contains two objects. Each object has a vertical size,
  115. that really doesn't need to grow bigger. In practice, this means that the
  116. whole window doesn't need to be resizable in the vertical direction, only in
  117. the horizontal direction. The horizontal size of the objects is automatically
  118. the same as the window's inner width. Now we take a look at the button
  119. gadgets. These are grouped in the lower horizontal group. If this group
  120. becomes wider, the "Cancel" button should move to the right too. Looking at
  121. the three objects in this group, we should allow the middle one (i.e. the
  122. empty spaceholder) to grow bigger. We do this by setting its weight to one.
  123. Both buttons can have a fixed size, so their weights are zero. An alternative
  124. is to give the buttons a weight of one too. That means that the available
  125. horizontal space will be evenly divided between the three objects, because
  126. each object has the same weight. As you can see, this is a very simple, yet
  127. powerful concept.
  128.  
  129. One thing to keep in mind is that if a group contains _no_ weighed objects,
  130. it is not filled completely. You're advised to add a custom image object with
  131. a weight of one in these cases. This will give the best results.
  132.  
  133.  
  134.      Designing the interface
  135.      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  136. The first thing you have to do, is make a sketch of the interface. You can
  137. either do this on a piece of paper, or by using some structured drawing tool.
  138. At this stage, you'll have to figure out how to divide the interface in
  139. groups of objects.
  140.  
  141. When you start designing an interface, make sure you've read The Amiga User
  142. Interface Style Guide[1]. It contains a lot of guidelines, and following
  143. these will make your interface easier to use.
  144.  
  145. Also, it is good practice to look at available software, and see how
  146. particular problems are solved. Especially the preference editors on your
  147. Workbench are good examples of how interfaces should look like (although they
  148. don't font adapt, unless you're a developer using a beta Release 3.1).
  149.  
  150.  
  151.      Using the interface
  152.      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  153. Basically, there are three main services you must provide to work with a
  154. window. First, you must initialize everything. Then you must handle incoming
  155. messages, until at some point the window is forced to close again, which is
  156. the third and last service. This is pretty straightforward.
  157.  
  158. Let's look at the initialization first. Creating a new object is quite
  159. simple, and anybody who has used BOOPSI will be familiar with the type of
  160. function call. All you do is call ea_NewObjectA(). This function takes the
  161. object type as the first argument, and a pointer to a taglist as the second
  162. one. Alternatively, you can use ea_NewObject(), which allows you to pass the
  163. individual tags as arguments. If all went well, the function will return a
  164. pointer to the object, which can be used as a handle. It is not encouraged
  165. nor allowed to make assumptions about the data that the pointer points to.
  166. The ea_NewObjectA() function can be used to create a whole tree easily. For
  167. more information, look at the AutoDocs. Let's take another look at our
  168. example:
  169.  
  170.      LONG init(VOID)
  171.      {
  172.           if (!(winobj_ptr = ea_NewObject(EA_TYPE_VGROUP,
  173.                EA_Child,      ea_NewObject(EA_TYPE_GTGADGET,
  174.                     EA_GTType,     STRING_KIND,
  175.                     [...]
  176.                     ),
  177.                EA_Child,      ea_NewObject(EA_TYPE_HGROUP,
  178.                     EA_Child,      ea_NewObject(EA_TYPE_GTGADGET,
  179.                          EA_GTType,     BUTTON_KIND,
  180.                          EA_GTText,     "Ok",
  181.                          [...]
  182.                          ),
  183.                     EA_Child,      ea_NewObject(EA_TYPE_CUSTOMIMAGE,
  184.                          [...]
  185.                          ),
  186.                     EA_Child,      ea_NewObject(EA_TYPE_GTGADGET,
  187.                          EA_GTType,     BUTTON_KIND,
  188.                          EA_GTText,     "Cancel",
  189.                          [...]
  190.                          ),
  191.                     [...]
  192.                     ),
  193.                [...]
  194.                )))
  195.           {
  196.                Printf("Couldn't create object tree.\n");
  197.                return(20);
  198.           }
  199.           return(0);
  200.      }
  201.  
  202. Some tags have been left out here, as marked by the ellipsis [...], because
  203. they are not essential to the example. Some other tags must be specified for
  204. GadTools or BOOPSI gadgets, and information on this can be found in the
  205. AutoDocs of these libraries. The most important thing is the way you can
  206. hierarchically define a tree. If something along the way goes wrong (which is
  207. usually because there wasn't enough memory) then nothing is created (all
  208. objects in the tree which were already created will be freed automatically),
  209. and NULL is returned.
  210.  
  211. Now that we've initialized a tree, we must also have a way to clean it up.
  212. This is even simpler. All we need is a simple call to ea_DisposeObject().
  213. This removes an object and all its children. In our example, it would look
  214. something like this:
  215.  
  216.      VOID cleanup(VOID)
  217.      {
  218.           ea_DisposeObject(winobj_ptr);
  219.      }
  220.  
  221. The next step is to calculate the minimum sizes. If you want the window to be
  222. resizable, then you must set the window sizing limits with the WindowLimits()
  223. call. Before that, you must first obtain the minimum sizes. This can be done
  224. easily by using ea_GetAttrs() on the `winobj_ptr'. The following example
  225. explains all this:
  226.  
  227.      /* obtain the minimum sizes of every object in the tree */
  228.      ea_GetMinSizes(winobj_ptr);
  229.  
  230.      /* get some attributes */
  231.      ea_GetAttrs(winobj_ptr,
  232.           EA_MinWidth,        &w,
  233.           EA_MinHeight,       &h,
  234.           EA_BorderLeft,      &bl,
  235.           EA_BorderRight,     &br,
  236.           EA_BorderTop,       &bt,
  237.           EA_BorderBottom,    &bb,
  238.           TAG_DONE);
  239.  
  240.      /* open the window */
  241.      win_ptr = OpenWindowTags([...]);
  242.  
  243.      /* set the window limits */
  244.      WindowLimits(
  245.           win_ptr,
  246.           w + win_ptr->BorderLeft + win_ptr->BorderRight + bl + br,
  247.           h + win_ptr->BorderTop + win_ptr->BorderBottom + bt + bb,
  248.           ~0,
  249.           h + win_ptr->BorderTop + win_ptr->BorderBottom + bt + bb);
  250.  
  251.      /* fill in the inner sizes of the window in the root object */
  252.      ea_SetAttrs(winobj_ptr,
  253.           EA_Width,      win_ptr->Width -
  254.                          win_ptr->BorderLeft -
  255.                          win_ptr->BorderRight -
  256.                          bl -
  257.                          br,
  258.           EA_Height,     win_ptr->Height -
  259.                          win_ptr->BorderTop -
  260.                          win_ptr->BorderBottom -
  261.                          bt -
  262.                          bb,
  263.           EA_Left,       win_ptr->BorderLeft,
  264.           EA_Top,        win_ptr->BorderTop,
  265.           TAG_DONE);
  266.  
  267.      /* now determine the object sizes and positions */
  268.      ea_LayoutObjects(winobj_ptr);
  269.  
  270.      /* create the list of gadgets for this window */
  271.      rc = ea_CreateGadgetList(winobj_ptr, &gadlist_ptr,
  272.           visualinfo_ptr, drawinfo_ptr);
  273.  
  274.      if (rc != 0)
  275.      {
  276.           /* bail out */
  277.           exit(20);
  278.      }
  279.  
  280.      /* now you're ready for business */
  281.  
  282. You can now handle events in exactly the same way you normally do. The only
  283. exception is the IDCMP_NEWSIZE event. This is where you can use EAGUI to
  284. adapt to the new window size. If you receive one of these, you must do the
  285. following things:
  286.  
  287.      a) Store any unsaved changes the user has made to the gadgets. This
  288. means storing strings that were entered in string gadgets, items that were
  289. selected in listviews, cycle gadgets or radio buttons, and things like that.
  290. Depending on your program structure, this may not be necessary at all,
  291. because the changes were already stored when the IDCMP message that notified
  292. that change was received.
  293.  
  294.      b) Remove the gadget list from the window, and clean it up, like this:
  295.  
  296.           RemoveGList(win_ptr, gadlist_ptr, -1);
  297.           ea_FreeGadgetList(winobj_ptr, gadlist_ptr);
  298.           gadlist_ptr = NULL;
  299.  
  300.      c) Examine the new dimensions of the window, and set the root object
  301. accordingly, like this:
  302.  
  303.           ea_GetAttrs(winobj_ptr,
  304.                EA_BorderLeft,      &bl,
  305.                EA_BorderRight,     &br,
  306.                EA_BorderTop,       &bt,
  307.                EA_BorderBottom,    &bb,
  308.                TAG_DONE);
  309.  
  310.           ea_SetAttrs(winobj_ptr,
  311.                EA_Width,           win_ptr->Width -
  312.                                    win_ptr->BorderLeft -
  313.                                    win_ptr->BorderRight -
  314.                                    bl -
  315.                                    br,
  316.                EA_Height,          win_ptr->Height -
  317.                                    win_ptr->BorderTop -
  318.                                    win_ptr->BorderBottom -
  319.                                    bt -
  320.                                    bb,
  321.                EA_Left,            win_ptr->BorderLeft,
  322.                EA_Top,             win_ptr->BorderTop,
  323.                TAG_DONE);
  324.  
  325.           ea_LayoutObjects(winobj_ptr);
  326.  
  327.      d) Build the gadget list again, and connect it to the window. Currently,
  328. the method I use to refresh the window looks somewhat strange. I haven't
  329. quite figured out what to do with it. It seems that GadTools refreshes the
  330. gadgets directly after resizing the window, but before you're notified of
  331. that fact. Therefore, it renders over your window borders. After that, you
  332. get a IDCMP_NEWSIZE message, and you have to redraw everything yourself
  333. (including the window borders). This is example of working code:
  334.  
  335.           rc = ea_CreateGadgetList(winobj_ptr, &gadlist_ptr,
  336.                visualinfo_ptr, drawinfo_ptr);
  337.           if (rc != 0)
  338.           {
  339.                /* bail out */
  340.                exit(20);
  341.           }
  342.           EraseRect(win_ptr->RPort,
  343.                win_ptr->BorderLeft,
  344.                win_ptr->BorderTop,
  345.                win_ptr->Width - win_ptr->BorderRight - 1,
  346.                win_ptr->Height - win_ptr->BorderBottom - 1);
  347.  
  348.           RefreshWindowFrame(win_ptr);
  349.  
  350.           AddGList(win_ptr, gadlist_ptr, -1, -1, NULL);
  351.           RefreshGList(gadlist_ptr, win_ptr, NULL, -1);
  352.           GT_RefreshWindow(win_ptr, NULL);
  353.  
  354.  
  355.      Includes and Macros
  356.      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  357. When using the library in your own program, there are certain headers that
  358. you need to include.
  359.      First, you must include "EAGUI.h", which contains all necessary
  360. information for using the library. This header then automatically includes
  361. "EAGUI_protos.h", which contains all needed function prototypes. There is one
  362. switch that may be of use. If you #define NOEAGUIMACROS before you include
  363. the header file, you won't get the macro's in "EAGUI_macros.h", otherwise you
  364. will. For normal circumstances, the macros seem to work well, but you may
  365. have your own personalized set of macro's that you're more comfortable with,
  366. so we won't force you to use them.
  367.      The second file you should include (at least when you're using SAS/C) is
  368. a pragmas file (called "EAGUI_pragmas.h"). Users of other languages may find
  369. the EAGUI.fd file useful. If you've written header files for any other
  370. language, and you want us to include them in the EAGUI package, please
  371. contact us.
  372.  
  373.  
  374.      Standard Methods
  375.      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  376. For all GadTools gadgets, minimum size and border methods are supplied in the
  377. library.
  378.  
  379. The methods that are supplied will try to adapt to all different tags that
  380. have an influence on the size of the object. The minimum size methods
  381. basically make the gadgets big enough to just fit. String gadgets for example
  382. will be high enough for the selected font, and wide enough for the border to
  383. be rendered succesfully. Border size methods will adapt to labels which are
  384. placed above, left of, right of or below a gadget. Please note that if, for
  385. example, you place a text above a gadget, the top border will only be high
  386. enough for the text to fit. If the text is _wider_ than the gadget, the
  387. gadget will not be adjusted: the label will simply be rendered, and you'll
  388. have to make sure that this doesn't conflict with other objects.
  389.  
  390. To use the standard methods, you must specify the EA_StandardMethod tag, like
  391. this:
  392.  
  393.      ea_NewObject([...]
  394.           EA_StandardMethod,  value,
  395.           [...]
  396.  
  397. The `value' parameter can be EASM_MINSIZE, EASM_BORDER or a combination of
  398. both (EASM_MINSIZE | EASM_BORDER), depending on what standard methods you
  399. want to use for this object. The EAGUI_macros.h file already uses this tag,
  400. so if you use these macros, the standard methods will be used automatically.
  401.  
  402.  
  403.      Creating your own Methods
  404.      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  405. Although the standard methods will be sufficient in many cases, there might
  406. be times when you want to create your own methods. A good example would be
  407. when you use BOOPSI gadgets. Other reasons for creating your own methods
  408. could be that your interface uses gadgets of a much simpler form, or that it
  409. only uses certain types of gadgets. Custom methods might improve execution
  410. speed slightly, since they don't need to be `universal'.
  411.  
  412. When creating you own methods, many things are possible, but there are a few
  413. things you have to keep in mind.
  414.  
  415. Every method is called using a callback hook. For more information about
  416. initializing and using hooks, please refer to the RKRM's[2] or the
  417. <utility/hooks.h> header file.
  418.  
  419. A method prototype should look like this:
  420.  
  421.      ULONG method(
  422.           struct Hook *       hook_ptr,
  423.           struct ea_Object *  object_ptr,
  424.           APTR                message_ptr);
  425.  
  426. The hook_ptr contains the pointer to the hook structure of the method.
  427.  
  428. The object_ptr points to the object that the method should be applied to.
  429.  
  430. The message_ptr is not used at the moment.
  431.  
  432. The method can change the attributes of the object. We strongly discourage
  433. you to let the method change any other attributes than the ones it should
  434. set. So a border method should only set the border attributes of the object,
  435. and a minsize method should only set the minsize attributes. You are allowed
  436. to read other attributes, if you need them for your calculations. Always use
  437. ea_GetAttrs() and ea_SetAttrs() to read and write the attributes
  438. respectively.
  439.  
  440. Note that when the object is created, it is possible to pass border, size or
  441. minsize attributes. EAGUI protects these values, so your method can't change
  442. them, even if it tries to. All this is done automatically, so you don't need
  443. to worry about this.
  444.  
  445.  
  446.      Relations
  447.      ¯¯¯¯¯¯¯¯¯
  448. In some interfaces, you want to create special relations between objects. One
  449. example that comes to mind is a simple window with an "Ok" and a "Cancel"
  450. button. If these both have a fixed size, then one of them will be bigger than
  451. the other, simply because the label strings don't have the same width. This
  452. does not look very good. To correct this, you can add a relation between the
  453. two objects. All objects in a relation must have the same direct parent. This
  454. relation is added to the parent of the objects. For example:
  455.  
  456.      ea_NewRelation(obj_hgroup_ptr, &relhook,
  457.           EA_Object,          obj_okgad_ptr,
  458.           EA_Object,          obj_cancelgad_ptr,
  459.           TAG_DONE);
  460.  
  461. Any number of objects can be connected using one and the same relation. You
  462. can simply specify a list of objects by using the tag shown above. Relations
  463. also use the Hook mechanism. They're called after the minimum sizes were
  464. calculated, so you can read the minimum sizes of the objects in this method.
  465.  
  466. To explain how to create relations, a simple example is included below:
  467.  
  468.      /* same size relation */
  469.      ULONG rel_samesize(struct Hook *hook_ptr, struct List *list_ptr,
  470.           APTR msg_ptr)
  471.      {
  472.           struct ea_RelationObject *ro_ptr;
  473.           ULONG minx, miny;
  474.           ULONG x, y;
  475.  
  476.           minx = 0;
  477.           miny = 0;
  478.  
  479.           /* examine the list of objects that are affected by the relation */
  480.           ro_ptr = (struct ea_RelationObject *)list_ptr->lh_Head;
  481.           while (ro_ptr->node.ln_Succ)
  482.           {
  483.                ea_GetAttrs(ro_ptr->object_ptr,
  484.                     EA_MinWidth,        &x,
  485.                     EA_MinHeight,       &y,
  486.                     TAG_DONE);
  487.  
  488.                /* find the maximum values of the minimum sizes */
  489.                minx = MAX(x, minx);
  490.                miny = MAX(y, miny);
  491.  
  492.                ro_ptr = (struct ea_RelationObject *)ro_ptr->node.ln_Succ;
  493.           }
  494.  
  495.           /* set all objects to the newly found minimum sizes */
  496.           ro_ptr = (struct ea_RelationObject *)list_ptr->lh_Head;
  497.           while (ro_ptr->node.ln_Succ)
  498.           {
  499.                ea_SetAttrs(ro_ptr->object_ptr,
  500.                     EA_MinWidth,        minx,
  501.                     EA_MinHeight,       miny,
  502.                     TAG_DONE);
  503.  
  504.                ro_ptr = (struct ea_RelationObject *)ro_ptr->node.ln_Succ;
  505.           }
  506.           return(0);
  507.      }
  508.  
  509. The example is pretty self-explaining.
  510.  
  511.  
  512.      Advanced Topics
  513.      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  514. There are no advanced topics discussed in this tutorial yet. This might
  515. change in future versions of this tutorial.
  516.  
  517.  
  518.      Bibliography
  519.      ¯¯¯¯¯¯¯¯¯¯¯¯
  520.  
  521.      [1] The Amiga User Interface Style Guide, Addison Wesley
  522.  
  523.      [2] The Amiga ROM Kernel Reference Manuals, Addison Wesley
  524.